package com.example.animations.activities;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import androidx.transition.Fade;
import androidx.transition.Slide;
import androidx.transition.Transition;
import androidx.transition.TransitionListenerAdapter;
import androidx.transition.TransitionSet;
import androidx.transition.TransitionValues;
import androidx.transition.Visibility;

import com.example.animations.R;
import com.example.animations.controls.PickerView;
import com.example.animations.controls.SliderView;
import com.example.animations.fragments.TransitionsFragment;
import com.example.animations.transitions.OffsetTransition;
import com.example.animations.transitions.ScaleTransition;

import java.util.ArrayList;
import java.util.Arrays;

public class TransitionsActivity extends AppCompatActivity implements TransitionsFragment.IOnDismissFragmentListener {
    private final TransitionsFragment _transitionsFragment = new TransitionsFragment(this);
    private final float minDuration = 0.2f;
    private final float maxDuration = 2.0f;
    private final AnimationPickerValue[] animationPickerValues = {
            new AnimationPickerValue("Linear", AnimationType.LINEAR),
            new AnimationPickerValue("Accelerate", AnimationType.ACCELERATE),
            new AnimationPickerValue("Decelerate", AnimationType.DECELERATE),
            new AnimationPickerValue("Accelerate & Decelerate", AnimationType.ACCELERATE_DECELERATE),
            new AnimationPickerValue("Anticipate", AnimationType.ANTICIPATE),
            new AnimationPickerValue("Overshoot", AnimationType.OVERSHOOT),
            new AnimationPickerValue("Anticipate & Overshoot", AnimationType.ANTICIPATE_OVERSHOOT),
            new AnimationPickerValue("Bounce", AnimationType.BOUNCE),
            new AnimationPickerValue("Cycle (3)", AnimationType.CYCLE),
            new AnimationPickerValue("Fast Out Linear In", AnimationType.FAST_OUT_LINEAR_IN),
            new AnimationPickerValue("Fast Out Slow In", AnimationType.FAST_OUT_SLOW_IN),
            new AnimationPickerValue("Linear Out Slow In", AnimationType.LINEAR_OUT_SLOW_IN)
    };
    private final TransitionPickerValue[] transitionPickerValues = {
            new TransitionPickerValue("Slide", TransitionType.SLIDE),
            new TransitionPickerValue("Scale", TransitionType.SCALE),
            new TransitionPickerValue("Opacity", TransitionType.OPACITY),
            new TransitionPickerValue("Move Top", TransitionType.MOVE_TOP),
            new TransitionPickerValue("Move Bottom", TransitionType.MOVE_BOTTOM),
            new TransitionPickerValue("Move Leading", TransitionType.MOVE_LEADING),
            new TransitionPickerValue("Move Trailing", TransitionType.MOVE_TRAILING),
            new TransitionPickerValue("Offset (200)", TransitionType.OFFSET_200),
            new TransitionPickerValue("Offset (400)", TransitionType.OFFSET_400),
            new TransitionPickerValue("Offset (-200)", TransitionType.OFFSET_M200),
            new TransitionPickerValue("Offset (-400)", TransitionType.OFFSET_M400)
    };
    private final ArrayList<Transition> _enterTransitionsList = new ArrayList(Arrays.asList(new Slide(Gravity.START), new Slide(Gravity.START)));
    private final ArrayList<Transition> _exitTransitionsList = new ArrayList(Arrays.asList(new Slide(Gravity.END), new Slide(Gravity.END)));

    private float _duration = 1.0f;
    private TransitionSet _enterTransitionSet;
    private TransitionSet _exitTransitionSet;
    private AnimationType _animationType;
    private PickerView _firstEnterTransitionPicker;
    private PickerView _secondEnterTransitionPicker;
    private boolean _isAsymmetricTransition = false;
    private TextView _exitTransitionTitleView;
    private PickerView _firstExitTransitionPicker;
    private PickerView _secondExitTransitionPicker;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_transitions);

        String[] animationTitles = Arrays.stream(animationPickerValues).map(PickerValue::getName).toArray(String[]::new);
        String[] transitionTitles = Arrays.stream(transitionPickerValues).map(PickerValue::getName).toArray(String[]::new);

        PickerView animationTypePicker = findViewById(R.id.animation_type_picker);
        animationTypePicker.setStringValues(animationTitles);
        animationTypePicker.setOnValueChangedListener(this::onAnimationTypeUpdated);

        _firstEnterTransitionPicker = findViewById(R.id.transition_enter_first_picker);
        _firstEnterTransitionPicker.setStringValues(transitionTitles);
        _firstEnterTransitionPicker.setOnValueChangedListener(i -> onEnterTransitionChanged(i, true));

        _secondEnterTransitionPicker = findViewById(R.id.transition_enter_second_picker);
        _secondEnterTransitionPicker.setStringValues(transitionTitles);
        _secondEnterTransitionPicker.setOnValueChangedListener(i -> onEnterTransitionChanged(i, false));

        _firstExitTransitionPicker = findViewById(R.id.transition_exit_first_picker);
        _firstExitTransitionPicker.setStringValues(transitionTitles);
        _firstExitTransitionPicker.setOnValueChangedListener(v -> onExitTransitionChanged(v, true));

        _secondExitTransitionPicker = findViewById(R.id.transition_exit_second_picker);
        _secondExitTransitionPicker.setStringValues(transitionTitles);
        _secondExitTransitionPicker.setOnValueChangedListener(v -> onExitTransitionChanged(v, false));

        _exitTransitionTitleView = findViewById(R.id.exit_transition_title_view);
        SwitchCompat asymmetricSwitch = findViewById(R.id.asymmetric_switch);
        asymmetricSwitch.setOnCheckedChangeListener((l, i) -> onAsymmetricTransitionChanged(i));
        asymmetricSwitch.setChecked(_isAsymmetricTransition);
        updateExitTransitionLayout();

        SliderView durationSlider = findViewById(R.id.duration_slider);
        durationSlider.setTitle("Duration");
        durationSlider.setMinMaxValues(minDuration, maxDuration);
        durationSlider.setValue(_duration);
        durationSlider.onValueChanged(this::onDurationChanged);

        ((Button) findViewById(R.id.show_button)).setOnClickListener(l -> showFragment());

        _enterTransitionSet = new TransitionSet();
        _exitTransitionSet = new TransitionSet();

        updateFragmentTransitions();
        onAnimationTypeUpdated(0);
        updateTransitionDuration();
    }

    private void onAsymmetricTransitionChanged(boolean asymmetric) {
        _isAsymmetricTransition = asymmetric;
        updateExitTransitionLayout();

        if (!_isAsymmetricTransition) {
            int firstIndex = _firstEnterTransitionPicker.getValue();
            int secondIndex = _secondEnterTransitionPicker.getValue();

            _firstExitTransitionPicker.setValue(firstIndex);
            _secondExitTransitionPicker.setValue(secondIndex);

            _exitTransitionsList.clear();
            _exitTransitionsList.add(getTransitionByIndex(firstIndex, false));
            _exitTransitionsList.add(getTransitionByIndex(secondIndex, false));

            updateFragmentTransitions();
        }
    }

    private void onAnimationTypeUpdated(int i) {
        AnimationType newType = animationPickerValues[i].getAnimationType();
        if (_animationType == newType) return;

        _animationType = newType;

        TimeInterpolator interpolator;

        switch (_animationType) {
            case ACCELERATE:
                interpolator = new AccelerateInterpolator();
                break;
            case DECELERATE:
                interpolator = new DecelerateInterpolator();
                break;
            case ACCELERATE_DECELERATE:
                interpolator = new AccelerateDecelerateInterpolator();
                break;
            case ANTICIPATE:
                interpolator = new AnticipateInterpolator();
                break;
            case OVERSHOOT:
                interpolator = new OvershootInterpolator();
                break;
            case ANTICIPATE_OVERSHOOT:
                interpolator = new AnticipateOvershootInterpolator();
                break;
            case BOUNCE:
                interpolator = new BounceInterpolator();
                break;
            case CYCLE:
                interpolator = new CycleInterpolator(3);
                break;
            case FAST_OUT_LINEAR_IN:
                interpolator = new FastOutLinearInInterpolator();
                break;
            case FAST_OUT_SLOW_IN:
                interpolator = new FastOutSlowInInterpolator();
                break;
            case LINEAR_OUT_SLOW_IN:
                interpolator = new LinearOutSlowInInterpolator();
                break;
            case LINEAR:
            default:
                interpolator = new LinearInterpolator();
                break;
        }

        _enterTransitionSet.setInterpolator(interpolator);
        _exitTransitionSet.setInterpolator(interpolator);
    }

    @Override
    public void onDismissFragment() {
        hideFragment();
    }

    private void showFragment() {
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, _transitionsFragment, "transition_fragment")
                .commit();
    }

    private void hideFragment() {
        getSupportFragmentManager().beginTransaction()
                .remove(_transitionsFragment)
                .commit();
    }

    private void onDurationChanged(float value) {
        _duration = value;
        updateTransitionDuration();
    }

    private void onEnterTransitionChanged(int i, boolean first) {
        Transition enterTransition = getTransitionByIndex(i, true);
        int index = first ? 0 : 1;

        _enterTransitionsList.remove(index);
        _enterTransitionsList.add(index, enterTransition);

        if (!_isAsymmetricTransition) {
            Transition exitTransition = getTransitionByIndex(i, false);
            _exitTransitionsList.remove(index);
            _exitTransitionsList.add(index, exitTransition);
            if (first) {
                _firstExitTransitionPicker.setValue(i);
            } else {
                _secondExitTransitionPicker.setValue(i);
            }
        }

        updateFragmentTransitions();
    }

    private void onExitTransitionChanged(int i, boolean first) {
        Transition transition = getTransitionByIndex(i, false);
        int index = first ? 0 : 1;

        _exitTransitionsList.remove(index);
        _exitTransitionsList.add(index, transition);

        updateFragmentTransitions();
    }

    private Transition getTransitionByIndex(int i, boolean enter) {
        TransitionType type = transitionPickerValues[i].getTransitionType();

        switch (type) {
            case OPACITY:
                return new Fade(enter ? Fade.MODE_IN : Fade.MODE_OUT);
            case SCALE:
                return new ScaleTransition();
            case MOVE_TOP:
                return new Slide(Gravity.TOP);
            case MOVE_BOTTOM:
                return new Slide(Gravity.BOTTOM);
            case MOVE_LEADING:
                return new Slide(Gravity.START);
            case MOVE_TRAILING:
                return new Slide(Gravity.END);
            case OFFSET_200:
                return new OffsetTransition(200f);
            case OFFSET_400:
                return new OffsetTransition(400f);
            case OFFSET_M200:
                return new OffsetTransition(-200f);
            case OFFSET_M400:
                return new OffsetTransition(-400f);
            case SLIDE:
            default:
                return new Slide(enter ? Gravity.START : Gravity.END);
        }
    }

    private void updateTransitionDuration() {
        _enterTransitionSet.setDuration(Math.round(_duration * 1000));
        _exitTransitionSet.setDuration(Math.round(_duration * 1000));
    }

    private void updateFragmentTransitions() {
        // Remove old transitions
        for (int i = _enterTransitionSet.getTransitionCount() - 1; i >= 0; i--) {
            Transition t = _enterTransitionSet.getTransitionAt(i);
            if (t != null) _enterTransitionSet.removeTransition(t);
        }
        for (int i = _exitTransitionSet.getTransitionCount() - 1; i >= 0; i--) {
            Transition t = _exitTransitionSet.getTransitionAt(i);
            if (t != null) _exitTransitionSet.removeTransition(t);
        }

        _enterTransitionsList.forEach(t -> {
            _enterTransitionSet.addTransition(t);
        });
        _exitTransitionsList.forEach(t -> {
            _exitTransitionSet.addTransition(t);
        });

        _transitionsFragment.setEnterTransition(_enterTransitionSet);
        _transitionsFragment.setExitTransition(_exitTransitionSet);
    }

    private void updateExitTransitionLayout() {
        _exitTransitionTitleView.setAlpha(_isAsymmetricTransition ? 1f : 0.5f);
        _firstExitTransitionPicker.setEnabled(_isAsymmetricTransition);
        _secondExitTransitionPicker.setEnabled(_isAsymmetricTransition);
    }

    private enum AnimationType {
        ACCELERATE_DECELERATE,
        LINEAR,
        ACCELERATE,
        DECELERATE,
        ANTICIPATE,
        ANTICIPATE_OVERSHOOT,
        OVERSHOOT,
        BOUNCE,
        CYCLE,
        FAST_OUT_LINEAR_IN,
        FAST_OUT_SLOW_IN,
        LINEAR_OUT_SLOW_IN
    }

    private enum TransitionType {
        SLIDE,
        SCALE,
        OPACITY,
        MOVE_TOP,
        MOVE_BOTTOM,
        MOVE_LEADING,
        MOVE_TRAILING,
        OFFSET_200,
        OFFSET_400,
        OFFSET_M200,
        OFFSET_M400
    }

    private abstract class PickerValue {
        private final String _name;

        public PickerValue(String name) {
            _name = name;
        }

        public String getName() {
            return _name;
        }
    }

    private class AnimationPickerValue extends PickerValue {
        private final AnimationType _animationType;

        public AnimationPickerValue(String name, AnimationType type) {
            super(name);
            _animationType = type;
        }

        public AnimationType getAnimationType() {
            return _animationType;
        }
    }

    private class TransitionPickerValue extends PickerValue {
        private final TransitionType _transitionType;

        public TransitionPickerValue(String name, TransitionType type) {
            super(name);
            _transitionType = type;
        }

        public TransitionType getTransitionType() {
            return _transitionType;
        }
    }
}
